Integrate 3rd Party Javascript Libraries In ZK Using Clientside Controller
Ashish Dasnurkar, Engineer, Potix Corporation
February 07, 2013
ZK 6.0 and later
Overview
In earlier article I introduced how you could integrate a 3rd party JS library with little knowledge of ZK's client side programming techniques. In this part I will demonstrate few advanced concepts of ZK client side programming allowing you to define a client side controller to encapsulate 3rd party JS library integration to make it more readable, reusable and easy to maintain.
Please note that for brevity I will only show code snippets to describe important parts of the code. For complete source code see Downloads section
Advanced concepts orientation
I will first describe few key concepts and ideas on how I will use them in the following sample.
Widget
Suppose you define a component for eg. window as shown below in your zul file
<window title="ZK Rocks" border="normal">
</window>
Now whenever ZK processes this zul file ZK creates two parts;a component represented as a Java object of org.zkoss.zul.Window
class that resides on the server side and a widget, as represented by a Javascript object that resides in browser i.e. client side. Now here is the key, just like one can customize a component by extending its Java class on server side, a widget on client side can also be customized by extending it using ZK's Javascript APIs. For eg. you can extend Window widget in Javascript using zk.$extends(Class, Map, Map) as shown below
zul.wgt.MyWindowWidget = zk.$extends(zul.wnd.Window, {
...
}
Use attribute
So you have customized or created a new Widget but how do you instruct ZK to use this custom widget instead of default one? It can be done by specifying use
client attribute on component tag and specifying fully qualified Javascript class of your widget.
<window xmlns:w="client" w:use="zul.wgt.MyWindowWidget">
</window>
Lifecycle
Once the widget is created on client side it follows a simple lifecycle. For more specific details, refer to this section from ZK's component development essentials. Briefly speaking, first the DOM tree representing the visual part of widget is created and attached to html document, followed by a call to bind_
method. bind_
is where typically any widget events are registered and also widget specific initialization can be done. Similarly there is unbind_
method which is invoked when widget is detached from DOM tree to un-register any events and/or release data specific to widget to avoid memory leak.
Communicating with the widget on client side
Widget is just a Javascript object so you can call any method on it by first getting a reference to it using zk.Widget.$() API and calling specific method directly
Communicating with the widget from server side
Unlike communicating with serverside via events as introduced earlier, when you want to communicate with widget from serverside you can do it by sending a command to ZK's client engine. ZK provides a utility API Clients.response(AuResponse) for this and for example can be used as shown below
Clients.response(new AuInvoke(mycomp, "doRefresh", new string("new data")));
Here AuInvoke is a derived implementation of AuResponse that allows developer to identify widget, its method to be invoked and additionally any data that needs to be passed to it.
Now let's re-implement the same example using above concepts
SpcaeTree layout demo
Here is the demo of example
Here we use two SpaceTree layout from Infovis Javascript Toolkit. You can click on any of these two SpaceTree layouts to perform add node,refresh or change orientation.
Implementation details
I will follow the same common tasks involved in integrating 3rd party JS library with ZK as introduced earlier and show you how we can make use of the concepts described above.
Include library source
You can include Javascript files using script xml processing instruction on your ZUML page. For this sample I downloaded Infovis toolkit Javascript library file and included it as shown below
<?script src="jit.js" ?>
Also for better organization of source code I have created a separate JS file for my custom code for this sample and included it similarly
<?script src="sampleAdv.js" ?>
Setup basic components
To show the SpaceTree layout visualization we need a div component. I have also added a textbox and a button for adding a new node feature.
<zk>
<window id="mainWin" xmlns:w="client">
<vlayout>
<div sclass="container">
<div id="infovis" w:use="zul.wgt.InfovisDiv" width="600px" height="300px"/>
</div>
<groupbox width="600px">
<caption label="Add/Refresh Nodes"/>
<hlayout width="590px">
<textbox hflex="1" id="nodeTxt" />
<button id="add" label="Add" />
<button id="refresh" label="Refresh" />
</hlayout>
</groupbox>
</vlayout>
</window>
</zk>
In addition to this I will also create a custom widget for div that will host Infovis SpaceTree layout. For this we can use the zk.$extends(Class, Map, Map) API introduced earlier.
zk.load("zul.wgt", function () {
zul.wgt.InfovisDiv = zk.$extends(zul.wgt.Div, {
});
});
Prepare data for library
I prepare the data same as introduced in the first part. There is no change in the implemntation for this part. Please refer to same section from earlier smalltalk
Initilizate/Configure library
For initializing Infovis Javascript toolkit and configuring SpaceTree layout I will make use of bind_
life-cycle method described above.
zk.load("zul.wgt", function () {
zul.wgt.InfovisDiv = zk.$extends(zul.wgt.Div, {
bind_: function(desktop, skipper, after){
this.$supers('bind_', arguments);
// initialize and/or config 3rd party library here
},
_init: function(treeData){
var removing = false;
//init Spacetree
//Create a new ST instance
var wrapper = '' + this.$n().id; //this.$n() return the root html element of this widget
this._st = new $jit.ST({
'injectInto': wrapper,
// rest of the init function
}
this._refresh(treeData); // show the SpaceTree layout
},
_refresh: function(treeData) {
this._st.loadJSON(treeData);
//compute node positions and layout
this._st.compute();
//optional: make a translation of the tree
this._st.geom.translate(new $jit.Complex(-200, 0), "current");
//Emulate a click on the root node.
this._st.onClick(this._st.root);
//end
},
// rest of the widget methods
});
});
I maintain a widget variable named _st
to hold reference to SpaceTree layout instance. Also note that I have separated this process into several methods. bind_
method configures SpaceTree layout, _init
creates SpaceTree layout instance as it should be done only when SpaceTree layout is required to be shown and finally _refresh
method to load data into SapceTree layout as it might be required frequently and can be called several times later.
Note that whereas bind_
method of our widget will be invoked by ZK during the lifecycle of widget, we need to invoke _init
ourselves. Calling _init
,_refresh
or any other widget methods that in turn call Javascript library functions is described in the next section
Calling library functions
Typically library function calls will be done via widget methods. For eg. I create SpaceTree layout instance in _init method by calling $jit.ST API of Infovis Javascript toolkit. Now we can call this _init method in two ways i.e from client side or from server side using the concepts described above.
Call widget method on client side
Get the instance of Widget using zk.Widget.$() API and call method as a normal Javascript function call on object.
var myST = zk.Widget.$("$infovis");
myST._init(treeData); // treeData here is the JSON representation of the SpaceTree
Call widget method from server side
Invoke Clients.response(AuResponse) method by identifying component/widget, widget method and pass optional data
Clients.response(new AuInvoke(infovis, "_init", spaceTreeNode)); //spaceTreeNode is JSONAware object representing SpaceTreeNode instance
Why should I use this approach?
What are the pros and cons of choosing a client side controller approach described here over the simpler one introduced in earlier smalltalk?
Pros
- No gobal state created thereby avoiding the evils of global variables to maintain state related to 3rd party Javascript library and avoid any variable/function conflict.
- Object oriented style of programming
- Can create multiple widgets with using same client side controller aka Widget class.
Cons
- Requires better understanding of ZK Widget lifecycle & Javascript APIs (However all of which is required is covered here)
Summary
In this article I introduced you how you can integrate with 3rd party Javascript libraries using a custom client side controller. In the course I introduced few key advanced concepts of ZK's client-side programming and advantages/disadvantages.
Downloads
You can get complete source for the example described in this smalltalk from its github repository Either do
git clone https://github.com/kachhalimbu/jslibinteg
or download zip archive from downloads section and import project into Eclipse as "Existing Maven Project"
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |